/*
 * Copyright 2015 泛泛o0之辈
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.jfast.framework.web.core;

import cn.jfast.framework.base.SDKVersion;
import cn.jfast.framework.base.cache.CacheManager;
import cn.jfast.framework.base.cache.SDKProp;
import cn.jfast.framework.base.cache.cacheimpl.ConcurrentMapCacheManager;
import cn.jfast.framework.base.util.Assert;
import cn.jfast.framework.base.util.ClassUtils;
import cn.jfast.framework.base.util.StringUtils;
import cn.jfast.framework.jdbc.annotation.Dao;
import cn.jfast.framework.jdbc.annotation.Transaction;
import cn.jfast.framework.jdbc.db.DBProperties;
import cn.jfast.framework.jdbc.orm.DaoProxy;
import cn.jfast.framework.log.LogFactory;
import cn.jfast.framework.log.LogType;
import cn.jfast.framework.log.Logger;
import cn.jfast.framework.schedule.ScheduleExecuter;
import cn.jfast.framework.schedule.ScheduledJob;
import cn.jfast.framework.web.api.Api;
import cn.jfast.framework.web.api.ApiInvocation;
import cn.jfast.framework.web.api.ApiReqMethod;
import cn.jfast.framework.web.api.annotation.*;
import cn.jfast.framework.base.cache.Cache;
import cn.jfast.framework.web.aop.AopScope;
import cn.jfast.framework.web.aop.AopHandler;
import cn.jfast.framework.web.aop.TransactionAopHandler;
import cn.jfast.framework.web.aop.annotation.Aop;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public abstract class ApplicationContext {

    private Logger log = LogFactory.getLogger(LogType.Jfast, ApplicationContext.class);

    /** 系统缓存管理器 */
    public static CacheManager cacheManager = (CacheManager) new ConcurrentMapCacheManager();

    /** 系统配置文件解析 */
    private XmlDefinitionsReader loader = new XmlDefinitionsReader();

    protected Map<String,String> staticResourceMap;

    /**
     * 初始化系统上下文
     */
    protected void loadContext() {
        loadConfig();
        loadCache();

    }

    /**
     * 加载系统属性，打印系统信息
     */
    protected void loadConfig() {

        loader.parser();
        Map<String, String> propMap = loader.getPropMap();
        staticResourceMap = loader.getResourcesMap();

        if (null != propMap.get("jdbcDriver") && !"".equals(propMap.get("jdbcDriver")))
            DBProperties.JDBC_DRIVER_CLASS = propMap.get("jdbcDriver");
        if (null != propMap.get("jdbcPassword") && !"".equals(propMap.get("jdbcPassword")))
            DBProperties.JDBC_PASSWORD = propMap.get("jdbcPassword");
        if (null != propMap.get("jdbcUrl") && !"".equals(propMap.get("jdbcUrl")))
            DBProperties.JDBC_URL = propMap.get("jdbcUrl");
        if (null != propMap.get("jdbcUser") && !"".equals(propMap.get("jdbcUser")))
            DBProperties.JDBC_USER = propMap.get("jdbcUser");
        if (null != propMap.get("isDevMode") && !"".equals(propMap.get("isDevMode")))
            SDKProp.devMode = Boolean.valueOf(propMap.get("isDevMode"));
        if (null != propMap.get("characterEncoding") && !"".equals(propMap.get("characterEncoding")))
            SDKProp.encoding = propMap.get("characterEncoding");
        if (null != propMap.get("aesKey") && !"".equals(propMap.get("aesKey")))
            SDKProp.aesKey = propMap.get("aesKey");
        if (null != propMap.get("appId") && !"".equals(propMap.get("appId")))
            SDKProp.appId = propMap.get("appId");
        if (null != propMap.get("appSecret") && !"".equals(propMap.get("appSecret")))
            SDKProp.appSecret = propMap.get("appSecret");
        if (null != propMap.get("token") && !"".equals(propMap.get("token")))
            SDKProp.token = propMap.get("token");
        if (null != propMap.get("handlerApi") && !"".equals(propMap.get("handlerApi")))
            SDKProp.handler = propMap.get("handlerApi");
        if (null != propMap.get("isInfoEnable") && !"".equals(propMap.get("isInfoEnable")))
            SDKProp.isInfoEnable = Boolean.valueOf(propMap.get("isInfoEnable"));
        if (null != propMap.get("isDebugEnable") && !"".equals(propMap.get("isDebugEnable")))
            SDKProp.isDebugEnable = Boolean.valueOf(propMap.get("isDebugEnable"));
        if (null != propMap.get("isWarnEnable") && !"".equals(propMap.get("isWarnEnable")))
            SDKProp.isWarnEnable = Boolean.valueOf(propMap.get("isWarnEnable"));
        if (null != propMap.get("isErrorEnable") && !"".equals(propMap.get("isErrorEnable")))
            SDKProp.isErrorEnable = Boolean.valueOf(propMap.get("isErrorEnable"));

        log.info("[ JFast SDK 版本-%s ] JAVA版本号---- %s", SDKVersion.version(), SDKProp.javaVersion);
        log.info("[ JFast SDK 版本-%s ] JAVA安装目录 ---- %s", SDKVersion.version(), SDKProp.javaHome);
        log.info("[ JFast SDK 版本-%s ] Java类格式版本号 ---- %s", SDKVersion.version(), SDKProp.javaClassVersion);
        log.info("[ JFast SDK 版本-%s ] 操作系统名称 ---- %s", SDKVersion.version(), SDKProp.osName);
        log.info("[ JFast SDK 版本-%s ] 操作系统架构 ---- %s", SDKVersion.version(), SDKProp.osArch);
        log.info("[ JFast SDK 版本-%s ] 操作系统版本 ---- %s", SDKVersion.version(), SDKProp.osVersion);
        log.info("[ JFast SDK 版本-%s ] 用户账户名称 ---- %s", SDKVersion.version(), SDKProp.userName);
        log.info("[ JFast SDK 版本-%s ] 用户主目录 ---- %s", SDKVersion.version(), SDKProp.userHome);
        log.info("[ JFast SDK 版本-%s ] 用户当前工作目录 ---- %s", SDKVersion.version(), SDKProp.userDir);
        log.info("[ JFast SDK 版本-%s ] 当前主机IP ---- %s", SDKVersion.version(), SDKProp.hostIP);
        log.info("[ JFast SDK 版本-%s ] 当前主机名 ---- %s", SDKVersion.version(), SDKProp.hostName);
    }

    /**
     * 系统缓存
     */
    private void loadCache() {
        loadDaoCache();
        loadResourceCache();
        loadInterceptorCache();
        loadApiCache();
        loadScheduleJobCache();
    }

    /**
     * 缓存普通资源
     */
    public void loadResourceCache() {
        Cache resourceCache = cacheManager.getCache("resource");
        Cache daoCache = cacheManager.getCache("dao");
        List<Class<?>> resourceList = ClassUtils.scanClasses(Resource.class);
        for (Class<?> clazz : resourceList) {
            Resource resource = clazz.getAnnotation(Resource.class);
            String alias = "".equals(resource.name()) ? StringUtils.firstCharToLowerCase(clazz.getSimpleName()) : resource.name();
            Assert.isNull(daoCache.get(alias), "普通资源别名 [ " + alias + " ] 重复");
            Assert.isNull(resourceCache.get(alias), "普通资源别名 [ " + alias + " ] 与数据访问接口别名重复");
            log.info("[ JFast SDK 版本-%s ] 加载普通资源 ---- 别名 : %s | 访问路径 : %s", SDKVersion.version(), alias, clazz.getName());
            resourceCache.put(alias, clazz);
        }
    }

    /**
     * 缓存数据接口
     */
    public void loadDaoCache() {
        Cache daoCache = cacheManager.getCache("dao");
        Cache resourceCache = cacheManager.getCache("resource");
        List<Class<?>> daoList = ClassUtils.scanClasses(Dao.class);
        for (Class<?> clazz : daoList) {
            Dao dao = clazz.getAnnotation(Dao.class);
            String alias = "".equals(dao.value()) ? StringUtils.firstCharToLowerCase(clazz.getSimpleName()) : dao.value();
            Assert.isNull(daoCache.get(alias), "数据接口别名 [ " + alias + " ] 重复");
            Assert.isNull(resourceCache.get(alias), "数据接口别名 [ " + alias + " ] 与普通资源别名重复");
            log.info("[ JFast SDK 版本-%s ] 加载数据接口 ---- 别名 : %s | 访问路径 : %s", SDKVersion.version(), alias, clazz.getName());
            daoCache.put(alias, clazz);
        }
    }

    /**
     * 缓存Api
     */
    @SuppressWarnings("unchecked")
    private void loadApiCache() {

        Cache apiCache = cacheManager.getCache("api");
        List<Class<?>> actionList = ClassUtils.scanClasses(cn.jfast.framework.web.api.annotation.Api.class);

        String typeRoute;
        String methodRoute;
        ApiReqMethod apiMethod;
        Class<?> api;
        List<Class<? extends AopHandler>> methodAops;
        final List<Class<? extends AopHandler>> globalAops = new ArrayList<Class<? extends AopHandler>>(0);

        Map<Object, Object> cache = cacheManager.getCache("aop").getNativeCache();

        for (Object obj : cache.values())
            globalAops.add((Class<? extends AopHandler>) obj);

        for (Class<?> clazz : actionList) {
            api = clazz;

            typeRoute = clazz.getAnnotation(cn.jfast.framework.web.api.annotation.Api.class).value();

            for (Method method : clazz.getMethods()) {

                methodAops = new ArrayList<Class<? extends AopHandler>>(0);

                if (method.isAnnotationPresent(Aop.class)) {
                    methodAops.addAll(Arrays.asList(method.getAnnotation(Aop.class).value()));
                    methodAops.remove(AopHandler.class);
                }
                if (method.isAnnotationPresent(Transaction.class)) //事务拦截器必须防止在最接近api的位置
                    methodAops.add(TransactionAopHandler.class);

                if (method.isAnnotationPresent(Put.class)) {
                    methodRoute = method.getAnnotation(Put.class).value();
                    apiMethod = ApiReqMethod.PUT;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Get.class)) {
                    methodRoute = method.getAnnotation(Get.class).value();
                    apiMethod = ApiReqMethod.GET;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Post.class)) {
                    methodRoute = method.getAnnotation(Post.class).value();
                    apiMethod = ApiReqMethod.POST;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Delete.class)) {
                    methodRoute = method.getAnnotation(Delete.class).value();
                    apiMethod = ApiReqMethod.DELETE;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Search.class)) {
                    methodRoute = method.getAnnotation(Search.class).value();
                    apiMethod = ApiReqMethod.SEARCH;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Options.class)) {
                    methodRoute = method.getAnnotation(Options.class).value();
                    apiMethod = ApiReqMethod.OPTIONS;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Head.class)) {
                    methodRoute = method.getAnnotation(Head.class).value();
                    apiMethod = ApiReqMethod.HEAD;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Trace.class)) {
                    methodRoute = method.getAnnotation(Trace.class).value();
                    apiMethod = ApiReqMethod.TRACE;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Patch.class)) {
                    methodRoute = method.getAnnotation(Patch.class).value();
                    apiMethod = ApiReqMethod.PATCH;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Move.class)) {
                    methodRoute = method.getAnnotation(Move.class).value();
                    apiMethod = ApiReqMethod.MOVE;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Mkcol.class)) {
                    methodRoute = method.getAnnotation(Mkcol.class).value();
                    apiMethod = ApiReqMethod.MKCOL;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Connect.class)) {
                    methodRoute = method.getAnnotation(Connect.class).value();
                    apiMethod = ApiReqMethod.CONNECT;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Copy.class)) {
                    methodRoute = method.getAnnotation(Copy.class).value();
                    apiMethod = ApiReqMethod.COPY;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Lock.class)) {
                    methodRoute = method.getAnnotation(Lock.class).value();
                    apiMethod = ApiReqMethod.LOCK;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Unlock.class)) {
                    methodRoute = method.getAnnotation(Unlock.class).value();
                    apiMethod = ApiReqMethod.UNLOCK;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Proppatch.class)) {
                    methodRoute = method.getAnnotation(Proppatch.class).value();
                    apiMethod = ApiReqMethod.PROPPATCH;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }
                if (method.isAnnotationPresent(Propfind.class)) {
                    methodRoute = method.getAnnotation(Propfind.class).value();
                    apiMethod = ApiReqMethod.PROPFIND;
                    addApi(typeRoute,methodRoute,api,method,apiMethod,methodAops,globalAops,apiCache);
                }

            }

        }
    }

    private void addApi(String typeRoute,
                        String methodRoute,
                        Class<?> api,
                        Method method,
                        ApiReqMethod apiMethod,
                        List<Class<? extends AopHandler>> methodAops,
                        List<Class<? extends AopHandler>> globalAops,
                        Cache apiCache){
        Api newAction = new Api(typeRoute.trim(), methodRoute.trim(), api, method, apiMethod, methodAops, globalAops);
        Assert.isNull(apiCache.get(newAction.getApiRoute(), Api.class), "服务别名 [ " + newAction.getApiRoute() + " ] 重复");
        log.info("[ JFast SDK 版本-%s ] 加载服务 ---- 别名 : %s | 访问路径 : %s", SDKVersion.version(), newAction.getApiRoute(), api.getName() + "." + method.getName() + "()");
        apiCache.put(newAction.getApiRoute(), newAction);
    }

    /**
     * 缓存拦截器
     */
    private void loadInterceptorCache() {
        Cache aopCache = cacheManager.getCache("aop");
        List<Class<?>> aops = ClassUtils.scanClasses(Aop.class);
        for (Class<?> clazz : aops) {
            if (AopHandler.class.isAssignableFrom(clazz)) {
                if (clazz.isAnnotationPresent(Aop.class)) {
                    Aop aop = clazz.getAnnotation(Aop.class);
                    if (aop.scope() == AopScope.Method)
                        continue;
                    String alias = StringUtils.isEmpty(aop.alias()) ? StringUtils.firstCharToLowerCase(clazz.getSimpleName()) : aop.alias();
                    Assert.isNull(aopCache.get(alias), "拦截器别名 [ " + alias + " ] 重复");
                    log.info("[ JFast SDK 版本-%s ] 加载全局拦截器 ---- 别名 : %s | 访问路径 : %s", SDKVersion.version(), alias, clazz.getName());
                    aopCache.put(alias, clazz);
                }
            } else {
                throw new RuntimeException("[ JFast SDK 版本-%s ] 启动失败 ：拦截器"+clazz.getSimpleName()+"未继承AopHandler类");
            }
        }
    }

    /**
     * 缓存定时任务
     */
    private void loadScheduleJobCache() {
        Cache jobCache = cacheManager.getCache("job");
        List<Class<?>> jobList = ClassUtils.scanClasses(cn.jfast.framework.web.api.annotation.ScheduleJob.class);
        String jobStr;
        Object obj = null;
        for (Class<?> jobClass : jobList) {
            try {
                obj = jobClass.newInstance();
                obj = fillResource(obj);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            for (Method method : jobClass.getMethods()) {
                Schedule schedule = method.getAnnotation(Schedule.class);
                if (null != schedule) {
                    ScheduledJob job = new ScheduledJob(
                            obj, method,
                            schedule.cron(), schedule.delay(),
                            schedule.repeat(), schedule.repeatInterval());
                    jobStr = StringUtils.firstCharToLowerCase(jobClass.getSimpleName() + "." + method.getName());
                    Assert.isNull(jobCache.get(jobStr), "定时任务别名 [ " + jobStr + " ] 重复");
                    log.info("[ JFast SDK 版本-%s ] 加载定时任务 ---- 别名 : %s | 访问路径 : %s", SDKVersion.version(), jobStr
                            , jobClass.getName() + "." + method.getName() + "()");
                    jobCache.put(jobStr, job);
                }
            }
        }
        ScheduleExecuter.me().initService(jobCache).start();
    }

    protected boolean handlerResource(String route, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        for(String resourceRoute:staticResourceMap.keySet()){
            if(route.startsWith(resourceRoute) && !route.startsWith(staticResourceMap.get(resourceRoute))){
                request.getRequestDispatcher(route.replaceFirst(resourceRoute,staticResourceMap.get(resourceRoute))).forward(request, response);
                return true;
            }
        }
        return false;
    }

    /**
     * 处理Api请求
     * @param route
     * @param request
     * @param response
     * @return
     */
    protected boolean handlerApi(String route, HttpServletRequest request,
                                 HttpServletResponse response) throws ServletException, IOException {

        ApiInvocation invoke = null;
        Api api = getApi(route + "/$" + request.getMethod().toLowerCase() + "$");
        if (null == api) {
            return false;
        }
        try {
            invoke = new ApiInvocation(api, api.getApi().newInstance(), request, response, route + "/$" + request.getMethod().toLowerCase() + "$");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        invoke.invoke();
        return true;

    }

    /**
     * 获得指定名称的数据接口
     * @param dao
     * @return
     */
    public static Object getDao(String dao) {
        Object obj = null;
        try {
            Class<?> clazz = (Class<?>) cacheManager.getCache("dao").get(dao);
            if (null != clazz)
                obj = new DaoProxy().getInstance((clazz).newInstance());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 获得指定名称的资源
     * @param resource
     * @return
     */
    public static Object getResource(String resource) {
        Object obj = null;
        try {
            Class<?> clazz = (Class<?>) cacheManager.getCache("resource").get(resource);
            if (null != clazz)
                obj = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 获得指定名称的Api
     * @param action
     * @return
     */
    public static Api getApi(String action) {
        return cacheManager.getCache("api").get(action, Api.class);
    }

    /**
     * jfast 对象注入
     * @param object
     * @return
     */
    public static Object fillResource(Object object) {
        Class<?> clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(Resource.class)) {
                Resource resource = field.getAnnotation(Resource.class);
                String alias = "".equals(resource.name()) ? StringUtils.firstCharToLowerCase(field.getName()) : resource.name();
                Object tempObj = getResource(alias);
                if (null == tempObj)
                    tempObj = getDao(alias);
                if (null == tempObj)
                    return object;
                Object resourceObj = fillResource(tempObj);
                try {
                    field.set(object, resourceObj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return object;
    }

    public void destroy() {
        ScheduleExecuter.me().stop();
        cacheManager.clear();
    }

}
